/********************************************************************
 * This file includes functions to fix up the pb pin mapping results
 * after routing optimization
 *******************************************************************/
/* Headers from vtrutil library */
#include "vtr_assert.h"
#include "vtr_log.h"
#include "vtr_time.h"

/* Headers from vpr library */
#include "lut_utils.h"
#include "openfpga_lut_truth_table_fixup.h"
#include "pb_type_utils.h"
#include "vpr_utils.h"

/* Include global variables of VPR */
#include "globals.h"

/* begin namespace openfpga */
namespace openfpga {

/********************************************************************
 * Apply the fix-up to truth table of LUT according to its pin
 * rotation status by packer
 *
 * Note:
 *   - pb must represents a LUT pb in the graph and it should be primitive
 *******************************************************************/
static void fix_up_lut_atom_block_truth_table(
  const AtomContext& atom_ctx, t_pb* pb, const t_pb_routes& pb_route,
  VprClusteringAnnotation& vpr_clustering_annotation, const bool& verbose) {
  t_pb_graph_node* pb_graph_node = pb->pb_graph_node;
  t_pb_type* pb_type = pb->pb_graph_node->pb_type;

  VTR_ASSERT(LUT_CLASS == pb_type->class_type);

  for (int iport = 0; iport < pb_type->num_ports; ++iport) {
    /* We only care about input ports whose pins are equivalent */
    if (IN_PORT != pb_type->ports[iport].type ||
        true == pb_type->ports[iport].is_clock) {
      continue;
    }
    if (pb_type->ports[iport].equivalent == PortEquivalence::NONE) {
      continue;
    }
    /* Reach here, we have to apply a fix-up */
    AtomBlockId atom_blk = atom_ctx.nlist.find_block(pb->name);
    VTR_ASSERT(atom_blk);

    /* Port exists (some LUTs may have no input and hence no port in the atom
     * netlist) */
    AtomPortId atom_port =
      atom_ctx.nlist.find_atom_port(atom_blk, pb_type->ports[iport].model_port);
    if (!atom_port) {
      continue;
    }

    /* Find the pin rotation status and record it ,
     * Note that some LUT inputs may not be used, we set them to be open by
     * default
     */
    std::vector<int> rotated_pin_map(pb_type->ports[iport].num_pins, -1);
    for (int ipin = 0; ipin < pb_type->ports[iport].num_pins; ++ipin) {
      int node_index =
        pb_graph_node->input_pins[iport][ipin].pin_count_in_cluster;
      if (pb_route.count(node_index)) {
        /* The pin is mapped to a net, find the original pin in the atom netlist
         */
        AtomNetId atom_net = pb_route[node_index].atom_net_id;

        VTR_ASSERT(atom_net);

        for (AtomPinId atom_pin : atom_ctx.nlist.port_pins(atom_port)) {
          AtomNetId atom_pin_net = atom_ctx.nlist.pin_net(atom_pin);

          if (atom_pin_net == atom_net) {
            rotated_pin_map[ipin] = atom_ctx.nlist.pin_port_bit(atom_pin);
            break;
          }
        }
      }
    }

    /* We can apply truth table adaption now
     * For unused inputs : insert dont care
     * For used inputs : find the bit in the truth table rows and move it by the
     * given mapping
     */
    const AtomNetlist::TruthTable& orig_tt =
      atom_ctx.nlist.block_truth_table(atom_blk);
    const AtomNetlist::TruthTable& adapt_tt =
      lut_truth_table_adaption(orig_tt, rotated_pin_map);
    vpr_clustering_annotation.adapt_truth_table(pb, adapt_tt);

    /* Print info is in the verbose mode */
    VTR_LOGV(verbose, "Original truth table\n");
    VTR_LOGV(verbose, "Index: ");
    for (size_t i = 0; i < rotated_pin_map.size(); ++i) {
      if (0 < i) {
        VTR_LOGV(verbose, ",");
      }
      VTR_LOGV(verbose, "%lu", i);
    }
    VTR_LOGV(verbose, "\n");
    for (const std::string& tt_line : truth_table_to_string(orig_tt)) {
      VTR_LOGV(verbose, "\t%s\n", tt_line.c_str());
    }
    VTR_LOGV(verbose, "\n");
    VTR_LOGV(verbose, "Pin rotation map: ");
    for (size_t i = 0; i < rotated_pin_map.size(); ++i) {
      if (0 < i) {
        VTR_LOGV(verbose, ",");
      }
      if (-1 == rotated_pin_map[i]) {
        VTR_LOGV(verbose, "open");
      } else {
        VTR_LOGV(verbose, "%lu", rotated_pin_map[i]);
      }
    }
    VTR_LOGV(verbose, "\n");
    VTR_LOGV(verbose, "Adapt truth table\n");
    for (const std::string& tt_line : truth_table_to_string(adapt_tt)) {
      VTR_LOGV(verbose, "\t%s\n", tt_line.c_str());
    }
    VTR_LOGV(verbose, "\n");
  }
}

/********************************************************************
 * This function recursively visits the pb graph until we reach a
 * LUT pb_type (primitive node in the pb_graph with a class type
 * of LUT_CLASS
 * Once we find a LUT node, we will apply the fix-up
 *******************************************************************/
static void rec_adapt_lut_pb_tt(
  const AtomContext& atom_ctx, t_pb* pb, const t_pb_routes& pb_route,
  VprClusteringAnnotation& vpr_clustering_annotation, const bool& verbose) {
  t_pb_graph_node* pb_graph_node = pb->pb_graph_node;

  /* If we reach a primitive pb_graph node, we return */
  if (true == is_primitive_pb_type(pb_graph_node->pb_type)) {
    if (LUT_CLASS == pb_graph_node->pb_type->class_type) {
      /* Do fix-up here.
       * Note that LUTs have two modes,
       * For wire modes, we should skip the truth table adaption
       * mode 0 is reserved for wire, see read_xml_arch_file.cpp
       * mode 1 is the regular mode
       */
      if (1 == pb->mode) {
        fix_up_lut_atom_block_truth_table(atom_ctx, pb->child_pbs[0], pb_route,
                                          vpr_clustering_annotation, verbose);
      }
    }
    return;
  }

  /* Recursively visit all the used pbs in the graph */
  t_mode* mapped_mode = &(pb_graph_node->pb_type->modes[pb->mode]);
  for (int ipb = 0; ipb < mapped_mode->num_pb_type_children; ++ipb) {
    /* Each child may exist multiple times in the hierarchy*/
    for (int jpb = 0; jpb < mapped_mode->pb_type_children[ipb].num_pb; ++jpb) {
      /* See if we still have any pb children to walk through */
      if ((pb->child_pbs[ipb] != nullptr) &&
          (pb->child_pbs[ipb][jpb].name != nullptr)) {
        rec_adapt_lut_pb_tt(atom_ctx, &(pb->child_pbs[ipb][jpb]), pb_route,
                            vpr_clustering_annotation, verbose);
      }
    }
  }
}

/********************************************************************
 * Main function to fix up truth table for each LUT used in FPGA
 * This function will walk through each clustered block
 *******************************************************************/
void update_lut_tt_with_post_packing_results(
  const AtomContext& atom_ctx, const ClusteringContext& clustering_ctx,
  VprClusteringAnnotation& vpr_clustering_annotation, const bool& verbose) {
  for (auto blk_id : clustering_ctx.clb_nlist.blocks()) {
    rec_adapt_lut_pb_tt(atom_ctx, clustering_ctx.clb_nlist.block_pb(blk_id),
                        clustering_ctx.clb_nlist.block_pb(blk_id)->pb_route,
                        vpr_clustering_annotation, verbose);
  }
}

} /* end namespace openfpga */
